package engine;

import model.Model;

/**
 * Molecules are represented as amounts, and this grid is used as a storage.
 *
 * <p>For computation efficiency, molecules - because there are so many of them
 * and they are relatively simple - they are not modules as agents, and instead
 * space is discretized in homogeneous grains wherein each grain is modeled
 * using traditional system dynamics.</p>
 *
 * <p>Beware of accessing and modifying the layer from different threads.</p>
 *
 * @author Ricardo Cruz {@literal <ricardo.pdm.cruz@gmail.com>}
 * @since  2014-11-12
 */
public class LayerFluids
{
	public static final Parameter<Integer> dL =
		new Parameter<Integer>(-1, "fluids", "granularity-um", 20);
	public static final Parameter<Double> D =
		new Parameter<Double>(-1, "fluids", "diffusion-constant-um2/min", 10.);
	public static final Parameter<Double> lambda =
		new Parameter<Double>(-1, "fluids", "decay-per-min", 0.);

	// TODO: public enum Molecule {AB}
	private static final int W = Model.getPetriWidth()/dL.get();
	private static final int H = Model.getPetriHeight()/dL.get();

	private double m[], buf[];  // molecules

	public LayerFluids() {
		m = new double[W*H];
		buf = new double[W*H];
	}

	/**
	 * Produces a certain amount of molecules at a given position. Pass a
	 * negative value for consumption.
	 * @param x coordinate in um
	 * @param y coordinate in um
	 * @param n molecules to n, may be negative to consume
	 */
	public synchronized void secrete(double x, double y, double n) {
		if(isInside(x,y)) {
			int i = (int)(x/dL.get()), j = (int)(y/dL.get());
			buf[i+j*W] += n;
		}
	}

	/**
	 * Asks whether position x,y has a molecule. The position is in um units.
	 * @param x position in um
	 * @param y position in um
	 * @return how many molecules
	 */
	public synchronized double getAmount(double x, double y) {
		int i = (int)(x/dL.get()), j = (int)(y/dL.get());
		return getAmountIndex(i, j);
	}

	/**
	 * The amount of molecules by molecular units (deltaL um).
	 * @param i x-position in deltaL um
	 * @param j y-position in deltaL um
	 * @return molecular amount
	 */
	public synchronized double getAmountIndex(int i, int j) {
		if(i < 0 || j < 0 || i >= W || j >= H)
			return 0;
		return m[i+j*W];
	}

	/**
	 * Diffusion process to occur at discrete intervals.
	 */
	public synchronized void diffuse() {
		for(int i = 1; i < W-1; i++)
			for(int j = 1; j < H-1; j++) {
				double diffuse = (D.get()/(dL.get()*dL.get())) * (
					getAmountIndex(i-1,j) + getAmountIndex(i+1,j)
					+ getAmountIndex(i,j-1) + getAmountIndex(i,j+1)
					- 4*getAmountIndex(i,j));
				double decay = - lambda.get()*m[i+j*W];
				buf[i+j*W] = Math.max(0, buf[i+j*W] + diffuse + decay);
			}

		System.arraycopy(buf, 0, m, 0, W*H);
	}

	private synchronized boolean isInside(double x, double y) {
		return x >= 0 && x < Model.getPetriWidth() && y >= 0 && y < Model.getPetriHeight();
	}
}
